//
//  HDMediator.m
//  HDMediator
//
//  Created by apple on 2017/4/25.
//  Copyright © 2017年 com.devin.definesys. All rights reserved.
//

#import "HDMediator.h"
#import <objc/runtime.h>

#define performActionResult(code,data,msg) @{@"code":code,@"data":data,@"msg":msg}

@interface HDMediator ()

@property (nonatomic, strong) NSMutableDictionary *cachedTarget;

@end

@implementation HDMediator

#pragma mark - public methods
+ (instancetype)sharedInstance
{
    static HDMediator *mediator;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mediator = [[HDMediator alloc] init];
    });
    return mediator;
}


- (id)performActionWithSchemeUrl:(NSURL *)schemeUrl params:(NSDictionary *)params completion:(void (^)(NSDictionary *))completion {
    NSObject *nilObject = nil ;
    
    NSString *module = @"" ;
    NSString *target = @"" ;
    NSString *action = @"" ;
    
    //解析请求,合并参数
    NSMutableDictionary *allParams = [[NSMutableDictionary alloc] init];
    NSString *urlString = [schemeUrl query];
    for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
        NSArray *elts = [param componentsSeparatedByString:@"="];
        if([elts count] < 2) continue;
        [allParams setObject:[elts lastObject] forKey:[elts firstObject]];
    }
    if (params) {
        [allParams addEntriesFromDictionary:params];
    }
    NSArray<NSString *> *fragmentArray = [schemeUrl.fragment componentsSeparatedByString:@"/"];
    if (fragmentArray.count < 2) {
        if (completion) {
            completion(performActionResult(@(HDMediatorResultFragmentError),nilObject,@"fragment is error syntax, can use '/[target]/[action]' or /[module]/[target]/[action]"));
        }
        return @(NO) ;
    } else if(fragmentArray.count == 2) {
        target = fragmentArray[0];
        action = fragmentArray[1];
    } else {
        module = fragmentArray[0];
        action = fragmentArray[1];
        target = fragmentArray[2];
    }
    
    //此处是安全控制
    if([self allowScheme] && ![[self allowScheme] containsObject:schemeUrl.scheme]) {
        //返回错误
        if (completion) {
            completion(performActionResult(@(HDMediatorResultSchemeNotFound),nilObject,@"scheme is not allow"));
        }
        return @(NO);
        
    }
    
    if([self allowHosts] && ![[self allowHosts] containsObject:schemeUrl.host]) {
        //返回错误
        if (completion) {
            completion(performActionResult(@(HDMediatorResultHostNotFound),nilObject,@"hosts is not allow"));
        }
        return @(NO);
    }
    
    //执行指令成功
    id result = [self performTarget:target action:action params:allParams shouldCacheTarget:NO];
    if (completion) {
        if (result) {
            if (![@0 isEqual:[result valueForKey:@"code"]]) {
                completion(result);
            } else {
                NSString * msg = [NSString stringWithFormat:@"execute target-action class Target_%@ method Action_%@ success",target,action];
                completion(performActionResult(@(HDMediatorResultOK),result,msg));
            }
        } else {
            NSString * msg = [NSString stringWithFormat:@"execute target-action class Target_%@ method Action_%@ success",target,action];
            completion(performActionResult(@(HDMediatorResultOK),@{},msg));
            result = @{};
        }
    }
    
    return result ;
}

/*
 scheme://[target]/[action]?[params]

 url sample:
 aaa://targetA/actionB?id=1234
*/
- (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion
{
 
    //合并参数
    NSMutableDictionary *allParams = [[NSMutableDictionary alloc] init];
    NSString *urlString = [url query];
    for (NSString *param in [urlString componentsSeparatedByString:@"&"]) {
        NSArray *elts = [param componentsSeparatedByString:@"="];
        if([elts count] < 2) continue;
        [allParams setObject:[elts lastObject] forKey:[elts firstObject]];
    }
    
    //此处是安全控制
    if([self allowScheme] && ![[self allowScheme] containsObject:url.scheme]) {
        //返回错误
        if (completion) {
            completion(@{@"error":@"schema is not allow"});
        }
        return @(NO);
    }
    
    if([self allowHosts] && ![[self allowHosts] containsObject:url.host]) {
        //返回错误
        if (completion) {
            completion(@{@"error":@"hosts is not allow"});
        }
        return @(NO);
    }
    
    // 这个demo针对URL的路由处理非常简单，就只是取对应的target名字和method名字，但这已经足以应对绝大部份需求。如果需要拓展，可以在这个方法调用之前加入完整的路由逻辑
    NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
    id result = [self performTarget:url.host action:actionName params:allParams shouldCacheTarget:NO];
    if (completion) {
        if (result) {
            if ([result error]) {
                completion(result);
            } else {
                completion(@{@"code":@0,@"data":result,@"msg":[NSString stringWithFormat:@"execute target-action class Target_%@ method Action_%@ success",url.host,actionName]});
            }
        } else {
            completion(@{@"code":@0,@"data":@{},@"msg":[NSString stringWithFormat:@"execute target-action class Target_%@ method Action_%@ success",url.host,actionName]});
        }
    }
    
    return result;
}

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    NSObject *nilObject = nil ;
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    Class targetClass;
    
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }
    
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // 这里是处理无响应请求的地方之一，这个demo做得比较简单，如果没有可以响应的target，就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上，然后处理这种请求的
        return performActionResult(@(HDMediatorResultClassNotFound),nilObject,@"target class is not found");
    }
    
    if (shouldCacheTarget) {
        self.cachedTarget[targetClassString] = target;
    }
    
    if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
    } else {
        // 有可能target是Swift对象
        actionString = [NSString stringWithFormat:@"Action_%@WithParams:", actionName];
        action = NSSelectorFromString(actionString);
        if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
        } else {
            // 这里是处理无响应请求的地方，如果无响应，则尝试调用对应target的notFound方法统一处理
            SEL action = NSSelectorFromString(@"notFound:");
            if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
            } else {
                // 这里也是处理无响应请求的地方，在notFound都没有的时候，这个demo是直接return了。实际开发过程中，可以用前面提到的固定的target顶上的。
                [self.cachedTarget removeObjectForKey:targetClassString];
                
                return performActionResult(@(HDMediatorResultActionNotFound),nilObject,@"target action is not found");
            }
        }
    }
}

- (void)releaseCachedTargetWithTargetName:(NSString *)targetName
{
    NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    [self.cachedTarget removeObjectForKey:targetClassString];
}

#pragma mark - private method 

- (NSArray<NSString *> *) allowScheme {
    return @[@"iosDevinDefinesys"];
}

- (NSArray<NSString *> *) allowHosts {
    return @[@"com.devin.definesys"];
}

#pragma mark - getters and setters
- (NSMutableDictionary *)cachedTarget
{
    if (_cachedTarget == nil) {
        _cachedTarget = [[NSMutableDictionary alloc] init];
    }
    return _cachedTarget;
}

@end
